/*
 * Copyright 2008-2009 Aying.Net Powered by Fuchun.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.aying.util;

import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;

/**
 * 包含加、减、乘、除等等基本数学运算的常用方法的数学工具类。
 * <p />
 * 该工具类中的方法可满足于大部份精度需求较高的算术运算。
 * 
 * @author Fuchun
 * @version 1.0, 2009-7-15
 */
public class MathUtils extends Utils {

    /**
     * 对给定的两个数进行除法运算。运算的精度由 {@code scale} 参数（即小数点后的位数）指定。 <strong>单线程时，精度值
     * {@code scale} 超过 4000 时，会有一定的性能损失，运行的计算机不尽相同。</strong>
     * 
     * <pre>例如：
     * BigDecimal result1 = MathUtils.divide(1, 32, 4); // 4 表示小数点后保留的位数
     * // result1.toString() --&gt; 0.0313
     * BigDecimal result2 = MathUtils.divide(1, 3, 2, RoundingMode.UNNECESSARY);
     * // 抛出 ArithmeticException 异常
     * // 因为 1 / 3 = 0.3333.... 无限循环 但是指定 RoundingMode.UNNECESSARY 不需要舍入，即不能精确的表示结果。
     * </pre>
     * 
     * @param <T> 要进行除法运算的数字类型。
     * @param arg1 被除数。
     * @param arg2 除数。
     * @param scale {@code arg1 / arg2} 所得结果的精度（即商的小数点后的位数）。
     * @param roundingMode 最终商值采用的舍入模式，默认为 {@code RoundingMode.HALF_UP} - 四舍五入。
     * @return 返回 {@code arg1} 除以 {@code arg2} 的指定精度和舍入模式的值。
     * @throws ArithmeticException 如果 {@code arg2 == 0} 或者 roundingMode ==
     *         RoundingMode.UNNECESSARY 和 {@code scale} 不足以准确的表示相除的结果。
     */
    public static <T extends Number> BigDecimal divide(T arg1, T arg2, int scale,
            RoundingMode... roundingMode) {
        if(arg1 == null || arg1.doubleValue() == 0.0d) {
            return BigDecimal.ZERO;
        }
        if (arg2 == null || arg2.doubleValue() == 0.0d) {
            throw new ArithmeticException("进行除法运算时的除数(arg2)的值不能为 null 或 0。");
        }
        BigDecimal first = new BigDecimal(arg1.doubleValue());
        BigDecimal second = new BigDecimal(arg2.doubleValue());
        RoundingMode mode = (roundingMode == null || roundingMode.length == 0) ? RoundingMode.HALF_UP
                : roundingMode[0];
        try {
            return first.divide(second, scale, mode);
        } catch (Exception ex) {
            if ("Rounding necessary".equals(ex.getMessage())) {
                throw new ArithmeticException(
                        "指定了 RoundingMode.UNNECESSARY 舍入模式，但是指定的标度(scale)不足以表示相除的结果！");
            }
            throw new ArithmeticException(ex.getMessage());
        }
    }

    /**
     * 对给定的两个数进行乘法运算。运算的精度由 {@code scale} 参数（保留的结果位数）指定。
     * <p />
     * 如果 {@code arg1 == null} 或 {@code arg2 == null} 或两个参数任一为 0，结果为 {@code BigDecimal.ZERO}。
     * 
     * <pre>例如：
     * BigDecimal result1 = MathUtils.multiply(5.1f, 6.9f, 0); // --&gt; 精度为 0 表示自动计算(根据两个数字的类型)
     * // 结果 result1 为 35.189999828338613951927982270717620849609375
     * BigDecimal result2 = MathUtils.multiply(5.1f, 6.9f, 4);
     * // 结果 result2 为 35.19
     * BigDecimal result3 = MathUtils.multiply(5.1f, 6.9f, 4, RoundingMode.UNNECESSARY);
     * // result3 的应该产生的结果为 35.189999828338613951927982270717620849609375
     * // 但是指定了精度为4，而舍入模式为 RoundingMode.UNNECESSARY 不需要舍入
     * // 所以结果不能被精确的表示，抛出 ArithmeticException 异常。
     * </pre>
     * 
     * @param <T> 要进行乘法运算的数字类型。
     * @param arg1 乘数。
     * @param arg2 乘数。
     * @param scale {@code arg1 * arg2} 所得结果的位数精度（即乘积的总位数）。
     * @param roundingMode 最终乘积采用的舍入模式，默认为 {@code RoundingMode.HALF_UP} - 四舍五入。
     * @return 返回 {@code arg1 * arg2} 的指定精度和舍入模式的值。
     * @throws ArithmeticException 如果结果不准确，但是舍入模式为
     *         {@code RoundingMode.UNNECESSARY}。
     */
    public static <T extends Number> BigDecimal multiply(T arg1, T arg2, int scale,
            RoundingMode... roundingMode) {
        if ((arg1 == null || arg2 == null) || arg1.doubleValue() == 0.0d || arg2.doubleValue() == 0.0d) {
            return BigDecimal.ZERO;
        }
        BigDecimal first = new BigDecimal(arg1.doubleValue());
        BigDecimal second = new BigDecimal(arg2.doubleValue());
        RoundingMode mode = (roundingMode == null || roundingMode.length == 0) ? RoundingMode.HALF_UP
                : roundingMode[0];
        if (scale < 0) scale = 0;
        MathContext mc = new MathContext(scale, mode);
        try {
            return first.multiply(second, mc);
        } catch (ArithmeticException ex) {
            if ("Rounding necessary".equals(ex.getMessage())) {
                throw new ArithmeticException(
                        "指定了 RoundingMode.UNNECESSARY 舍入模式，但是指定的标度(scale)不足以表示相乘所得的精确结果！");
            }
            throw ex;
        }
    }
}
